package com.bibimaya.auto.mapper.dao.mapper.processor;

import com.bibimaya.auto.mapper.dao.annotation.AMapper;
import com.bibimaya.auto.mapper.dao.mapper.BaseMethodMapper;
import com.bibimaya.auto.mapper.dao.utils.EntityMap;
import com.bibimaya.auto.mapper.dao.utils.FieldMap;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.session.Configuration;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class MapperProcessor {

    private static final String RESULT_MAP_ID = ".BaseResultMap_";

    private static final String WHERE_APPEND_SUFFIX = "WhereAppend";

    private static final ThreadLocal<Configuration> configLocal = new ThreadLocal<>();

    private static final ThreadLocal<Boolean> isInitLocal = new ThreadLocal<>();

    private static final ThreadLocal<Class<? extends BaseMethodMapper>> mapperClassLocal = new ThreadLocal<>();

    private static final ThreadLocal<EntityMap> entityMapLocal = new ThreadLocal<>();

    private static final Map<String, MappedStatement> cacheMSMap = new HashMap<>();

    public boolean init(Configuration config, Class<? extends BaseMethodMapper> mapperClass, EntityMap entityMap){
        isInitLocal.set(true);
        configLocal.set(config);
        mapperClassLocal.set(mapperClass);
        entityMapLocal.set(entityMap);
        return isInitLocal.get();
    }

    public void make(){
        if(isInitLocal.get() == null || !isInitLocal.get()){
            throw new RuntimeException("MethodProcessor 未初始化!!!");
        }
        String mapperId = mapperId();
        if (configLocal.get().hasStatement(mapperId, false)) {
            return ;
        }
        synchronized(entityMapLocal.get().getC()) {
            MappedStatement ms = makeMapped(mapperId, sql(), commandType(), parameterType(), resultMaps());
            configLocal.get().addMappedStatement(ms);
        }
        isInitLocal.set(false);
    }

    protected abstract Class<? extends BaseMethodMapper> getMapperInterface();

    protected String mapperId(){
        Class c = getMapperInterface();
        return mapperClassLocal.get().getCanonicalName() + "." + c.getMethods()[0].getName();
    }

    protected abstract String sql();

    protected abstract SqlCommandType commandType();

    protected abstract Class parameterType();

    protected abstract ResultMap[] resultMaps();

    private MappedStatement makeMapped(String mapperId, String sql,
                                       SqlCommandType commandType,
                                       Class parameterType, ResultMap... resultMaps){
        MappedStatement ms;
        if (cacheMSMap.get(mapperId) == null) {
            SqlSource sqlSource = configLocal.get().getDefaultScriptingLanuageInstance().createSqlSource(configLocal.get(), sql, parameterType);
            if (resultMaps != null && resultMaps.length > 0) {
                ArrayList<ResultMap> resultMapList = new ArrayList<ResultMap>();
                for (int i = 0; i < resultMaps.length; i++) {
                    resultMapList.add(resultMaps[i]);
                }
                ms = new MappedStatement.Builder(configLocal.get(), mapperId, sqlSource, commandType)
                        .resultMaps(resultMapList).build();
            } else {
                ms = new MappedStatement.Builder(configLocal.get(), mapperId, sqlSource, commandType)
                        .build();
            }
            cacheMSMap.put(mapperId, ms);
        } else {
            ms = cacheMSMap.get(mapperId);
        }
        return ms;
    }

    protected String getWhereIncludeSql() {
        AMapper aMapper = (AMapper) mapperClassLocal.get().getDeclaredAnnotation(AMapper.class);

        String appendSqlId = null;
        if(aMapper != null){
            AMapper.WhereAppend[] whereAppends = aMapper.whereAppendMap();
            for(AMapper.WhereAppend w : whereAppends){
                if(this.getClass().equals(w.mapperInterface())){
                    appendSqlId = w.appendSqlId();
                    break;
                }
            }
        }

        String namespace = mapperClassLocal.get().getCanonicalName();
        String sqlIdVal = namespace + "." + mapperId() + WHERE_APPEND_SUFFIX;
        if(appendSqlId != null){
            sqlIdVal = namespace + "." + appendSqlId;
        }
        Map<String, XNode> sqlFragments = configLocal.get().getSqlFragments();
        XNode pNode = sqlFragments.getOrDefault(sqlIdVal, null);

        StringBuffer sql = new StringBuffer();
        if(pNode == null){
            if(appendSqlId != null){
                throw new RuntimeException("未找到appendSqlId的定义!!!");
            }
            return sql.toString();
        }
        List<XNode> childNodeList = pNode.getChildren();
        if(childNodeList != null && !childNodeList.isEmpty()){
            for(XNode node : childNodeList){
                sql.append(node.toString());
            }
        }
        return sql.toString();
    }

    protected String getBaseResultMapId(){
        return mapperClassLocal.get().getCanonicalName() + RESULT_MAP_ID;
    }
    protected ResultMap getBaseResultMap() {
        String baseResultMapId = getBaseResultMapId();
        if(configLocal.get().hasResultMap(baseResultMapId)) {
            return configLocal.get().getResultMap(baseResultMapId);
        }else{
            synchronized (entityMapLocal.get().getC()) {
                return createResultMap();
            }
        }
    }

    public ResultMap createResultMap() {
        String baseResultMapId = getBaseResultMapId();
        if(configLocal.get().hasResultMap(baseResultMapId)) {
            return configLocal.get().getResultMap(baseResultMapId);
        }
        List<ResultMapping> list = new ArrayList<ResultMapping>();
        for (FieldMap fm : entityMapLocal.get().getFields()) {
            ResultMapping rm = new ResultMapping.Builder(configLocal.get(), fm.getField(), fm.getColumn(), fm.getType()).build();
            list.add(rm);
        }
        ResultMap resultMap =  new ResultMap.Builder(configLocal.get(), baseResultMapId, entityMapLocal.get().getC(), list).build();
        configLocal.get().addResultMap(resultMap);
        return resultMap;
    }

    public ResultMap createIntegerResultMap() {
        String baseResultMapId = getBaseResultMapId();
        String integerResultMap = mapperClassLocal.get().getCanonicalName() + "-Inline";
        ResultMap inlineResultMap = new ResultMap.Builder(
                configLocal.get(),
                integerResultMap,
                Integer.class,
                new ArrayList<>(),
                null).build();
        return inlineResultMap;
    }

    public Class<? extends BaseMethodMapper> getMapperClass() {
        return mapperClassLocal.get();
    }

    public void setMapperClass(Class<? extends BaseMethodMapper> mapperClass) {
        mapperClassLocal.set(mapperClass);
    }

    public EntityMap getEntityMap() {
        return entityMapLocal.get();
    }

    public void setEntityMap(EntityMap entityMap) {
        entityMapLocal.set(entityMap);
    }


}
